/**
 * 图像控制类，用于处理图片相关操作
 * 合并图片、创建背景图片，创建文字等
 */
const path = require('path');
const sharp = require('sharp');
const TextToSVG = require('text-to-svg');
const axios = require('axios');

const Utils = require('./utils/index');
const request = axios.create({ timeout: 5000 });
const pickData = res => res.data;
const EMPTY_TEXT = ' ';

const textToSVG = TextToSVG.loadSync(
  path.join(process.cwd(), 'fonts/SimHei.ttf')
);

// sharp图像像素上限
// const limitInputPixels = 1000000000;

// 楼层划分函数算法
// 根据传入数组元素个数，生成拼接户型图图片行数和列数
const divideLevel = arr => {
  const length = arr.length;
  let cols = 0, rows = 0;

  if (length === 2) {
    rows = 1;
    cols = 2;
  } else if (length === 3) {
    rows = 1;
    cols = 3;
  } else if (length === 5) {
    rows = 2;
    cols = 3;
  } else if (length === 6) {
    rows = 2;
    cols = 3;
  } else if (length === 7) {
    rows = 2;
    cols = 4;
  } else if (length === 8) {
    rows = 2;
    cols = 4;
  } else {
    rows = Math.ceil(Math.sqrt(length));
    cols = Math.ceil(length / rows);
  }

  let index = 0;
  const result = [];

  for (let i = 0; i < rows; i++) {
    const row = [];

    for (let j = 0; j < cols; j++) {
      row.push(arr[index] ? arr[index] : null);

      index++;
    }
    result.push(row);
  }

  return result;
};

const divideLevel2 = arr => {
  const length = arr.length;
  let cols = 0, rows = 0;

  if (length === 2) {
    rows = 1;
    cols = 2;
  } else if (length === 3) {
    rows = 1;
    cols = 3;
  } else if (length === 5) {
    rows = 2;
    cols = 3;
  } else if (length === 6) {
    rows = 2;
    cols = 3;
  } else if (length === 7) {
    rows = 2;
    cols = 4;
  } else if (length === 8) {
    rows = 2;
    cols = 4;
  } else {
    rows = Math.ceil(Math.sqrt(length));
    cols = Math.ceil(length / rows);
  }

  let index = 0;
  const result = [];

  for (let i = 0; i < rows; i++) {
    const row = [];

    for (let j = 0; j < cols; j++) {
      row.push(arr[index] ? arr[index] : null);

      index++;
    }
    result.push(row);
  }

  return {
    rows,
    cols,
    data: result
  };
};

module.exports = class ImageController {
  constructor() {
    this.imageFormats = [
      'jpeg', 'png', 'webp',
      'gif', 'jp2', 'tiff', 'avif',
      'heif', 'jxl', 'raw'
    ];
  }

  async getMetadata(image) {
    return await sharp(image).metadata();
  }

  async getBuffer(filepath) {
    return await sharp(filepath).toBuffer();
  }

  async createBackground(options) {
    const { width, height, channels = 4, background = '#ffffff' } = options;

    [width, height, channels].forEach(props => {
      if (typeof props !== 'number') {
        throw new TypeError('width, height, channels should "number"');
      }
    });

    return sharp({
      create: {
        width,
        height,
        channels,
        background
      }
    });
  }

  async createText(text, options = {}) {
    if (Utils.isFalseValue(text)) text = EMPTY_TEXT;

    const {
      fill = 'black', stroke = 'black',
      x = 0, y = 0, fontSize = 36, anchor = 'top'
    } = options;

    const attributes = { fill, stroke };

    const svgOptions = {
      x, y,
      fontSize,
      anchor,
      attributes
    };

    const svgBuffer = Buffer.from(textToSVG.getSVG(text, svgOptions));

    return svgBuffer;
  }

  async trim(image) {
    return await sharp(image).trim().toBuffer();
  }

  // 给图片添加文字, 不改变原始尺寸
  async mergeText(text, image, options = {}) {
    if (Utils.isFalseValue(text)) text = EMPTY_TEXT;

    const { position = 'bottom', offset = 0, fontSize = 24 } = options;
    const { width, height } = await this.getMetadata(image);

    const textBuffer = await this.createText(text, {
      fontSize
    });

    const extendProp = {
      top: 0,
      right: 0,
      bottom: 0,
      left: 0
    };

    const flag = Object.keys(extendProp).includes(position);

    flag
      ? extendProp[position] = Math.ceil(offset)
      : extendProp['bottom'] = Math.ceil(offset);

    const extendImage = await this.extend(image, extendProp);

    const buffer = await sharp(extendImage)
      .composite([{
        input: textBuffer,
        top: Math.ceil(height + offset / 2) - 50,
        left: Math.ceil(width / 2) - 30
      }])
      .jpeg()
      .toBuffer();

    return await this.resize(buffer, { width, height });
  }

  // 给图片拼接第三方图片
  async mergeImage(originImage, spliceImage, options) {
    const { position = 'bottom' } = options;

    const { height: originHeight, width: originWidth } =
      await this.getMetadata(originImage);

    const { height: spliceImageHeight, width: spliceImageWidth } =
      await this.getMetadata(spliceImage);

    if (position !== 'bottom') {
      throw new Error('merge image position param is error');
    }

    // TODO: 支持多个位置拼接
    const [backgroundWidth, backgroundHeight] = [
      Math.max(originWidth, spliceImageWidth),
      Math.ceil(originHeight + spliceImageHeight)
    ];

    spliceImage = await this.resize(spliceImage, {
      width: backgroundWidth,
      height: spliceImageHeight
    });

    const background =
      await this.createBackground({
        width: backgroundWidth, height: backgroundHeight
      });

    const buffer = await background.composite([{
      input: originImage,
      top: 0,
      left: 0
    }, {
      input: spliceImage,
      top: originHeight,
      left: 0
    }]).jpeg().toBuffer();

    return await this.resize(buffer, {
      width: originWidth,
      height: originHeight
    });
  }

  // 给图片加背景
  async addBackground(color = '#ffffff', image) {
    const { width, height } = await this.getMetadata(image);
    const background = await this.createBackground({ width, height, background: color });

    return await background.composite([{
      input: image,
      blend: 'atop',
      top: 0,
      left: 0
    }]).jpeg().toBuffer();
  }

  /**
   * 合并户型图图片
   * @param {*Array} images
   * @param {*Object} options
   * @returns
   */
  async merge(images, options = {}) {
    // 楼层数量
    const totalLevel = images.length > 5 ? 5 : images.length;

    // 对原始数据分组处理
    const imageGroupList = divideLevel(images);
    let {
      channels = 4, background = '#ffffff', marginLeft = 0, marginTop = 0
    } = options;
    const imageInfoList = [];
    // 单张图片横向距离间距

    if (totalLevel === 4) {
      marginLeft = 200;
      marginTop = 200;
    }

    if (totalLevel === 5) {
      marginLeft = 400;
      marginTop = 400;
    }

    // 遍历
    for (const singleImage of images) {
      const { width, height } = await this.getMetadata(singleImage);

      imageInfoList.push({ width, height });
    }

    // 计算最大宽高
    const maxImageWidth = imageInfoList.reduce((prev, curr) => {
      return curr.width > prev.width ? curr : prev;
    }).width;

    const maxImageHeight = imageInfoList.reduce((prev, curr) => {
      return curr.height > prev.height ? curr : prev;
    }).height;

    // 计算整张图片的长度和宽度
    const row = imageGroupList.length;
    const column = imageGroupList[0].length;
    const maxBackgroundWidth = maxImageWidth * column + (column - 1) * marginLeft;
    const maxBackgroundHeight = maxImageHeight * row + (row - 1) * marginTop;

    const compositeList = [];

    for (let [i, group] of imageGroupList.entries()) {
      const top = i > 0 ? maxImageHeight * i + marginTop * i : 0;

      for (let [j, image] of group.entries()) {
        if (!image) {
          const blockBackground = await this.createBackground({
            width: maxImageWidth,
            height: maxImageHeight,
            channels: 4,
            background: '#ffffff'
          });

          image = await blockBackground.jpeg().toBuffer();
        }

        const left = j > 0 ? maxImageWidth * j + marginLeft * j : 0;

        const { width, height } = await this.getMetadata(image);

        const padding = {
          top: 0,
          bottom: 0,
          left: 0,
          right: 0
        };

        // 根据最大宽高，扩展每张图片
        if (maxImageHeight > height) {
          padding.top = Math.floor((maxImageHeight - height) / 2);
          padding.bottom = Math.floor((maxImageHeight - height) / 2);
        }

        if (maxImageWidth > width) {
          padding.left = Math.floor((maxImageWidth - width) / 2);
          padding.right = Math.floor((maxImageWidth - width) / 2);
        }

        image = await this.extend(image, { ...padding });

        // image = await this.addBorder(image)

        image = await this.resize(image, { width: 1440, height: 1080 })

        console.log()

        const input = {
          input: image,
          blend: 'atop',
          left,
          top,
        };

        compositeList.push(input);
      }
    }

    const maxBackground = await this.createBackground({
      width: maxBackgroundWidth,
      height: maxBackgroundHeight,
      channels,
      background
    });

    return await maxBackground.composite(compositeList).jpeg().toBuffer();
  }

  /**
   * 合并户型图图片2
   * @param {*} image 
   * @param {*} options 
   * @returns 
   */
  async merge2(images, options) {
    const { cols, rows, data: imageGroupList } = divideLevel2(images);
    const finalWidth = 1440;
    const finalHeight = 1080;

    // 计算每张图片的大小
    let resizeWidth = 0, resizeHeight = 0;
    let marginLeft = 0, marginTop = 0;

    resizeWidth = Math.floor(finalWidth / cols);
    resizeHeight = Math.floor(finalHeight / rows);

    const compositeList = [];

    // 第一层遍历，每一行
    for (let [i, group] of imageGroupList.entries()) {
      // 设置纵向偏移量
      const top = i > 0 ? resizeHeight * i + marginTop * i : 0;

      // 第二层遍历，每一列
      for (let [j, image] of group.entries()) {
        // 如果不存在图片对象，则使用空白背景代替
        if (!image) {
          const blockBackground = await this.createBackground({
            width: resizeWidth,
            height: resizeHeight,
            channels: 4,
            background: '#ffffff'
          });

          image = await blockBackground.jpeg().toBuffer();
        }

        // 设置横向偏移量
        const left = j > 0 ? resizeWidth * j + marginLeft * j : 0;

        // 转换图片大小
        image = await this.resize(image, {
          width: resizeWidth,
          height: resizeHeight
        });

        // 输出图片对象
        const input = {
          input: image,
          blend: 'atop',
          left,
          top,
        };

        compositeList.push(input);
      }
    }

    // 创建整体背景图
    const maxBackground = await this.createBackground({
      width: finalWidth,
      height: finalHeight,
      channels: 4,
      background: '#ffffff'
    });

    // 输出拼接后的图片
    return await maxBackground.composite(compositeList).jpeg().toBuffer();
  }

  // 美化图片: 给图片加背景/padding、文字等操作
  async beautify(image, options = {}) {
    const defaultBackgroundProps = {
      channels: 4,
      background: '#ffffff'
    };

    const {
      width, height,
      name = ' ',
      paddingBottom = 100
    } = options;

    const background = await this.createBackground({
      width,
      height,
      ...defaultBackgroundProps
    });

    const beautifyImage = await background.composite([{
      input: image,
      blend: 'atop',
      left: 0,
      top: 0,
    }])
      .jpeg()
      .toBuffer();

    if (name !== EMPTY_TEXT && !Utils.isFalseValue(name)) {
      const text = await this.createText(name);

      return await sharp(beautifyImage)
        .extend({
          top: 0,
          right: 0,
          bottom: paddingBottom,
          left: 0,
          background: '#ffffff'
        })
        .composite([{
          input: text,
          blend: 'atop',
          left: width / 2,
          top: height + 20,
        }])
        .jpeg()
        .toBuffer();
    }

    return beautifyImage;
  }

  // 调整尺寸
  async resize(image, options = {}) {
    const {
      width = 1200, height = 900,
      fit = 'contain', background = '#ffffff'
    } = options;

    const buffer = await sharp(image)
      .resize({
        width,
        height,
        fit,
        background
      })
      .toBuffer();

    return buffer;
  }

  // 生成图片
  async toFile(buffer, filepath) {
    return await sharp(buffer).toFile(filepath);
  }

  // 扩展图片
  async extend(image, options = {}) {
    const {
      top = 0, right = 0, bottom = 0, left = 0, background = '#ffffff'
    } = options;

    if ([top, right, bottom, left].every(v => v === 0)) return image;

    const buffer = await sharp(image)
      .extend({
        top,
        right,
        bottom,
        left,
        background
      })
      .toBuffer();

    return buffer;
  }

  // 添加边框
  async addBorder(image, options = {}) {
    const { border = 1, color = '#000000' } = options;
    const { width, height } = this.getMetadata(image);

    const buffer = await this.extend(image, {
      top: border,
      left: border,
      right: border,
      bottom: border,
      background: color
    });

    return await this.resize(buffer, { width, height });
  }

  // 读取远程图片
  async readRemoteImage(imageURL) {
    return await request
      .get(imageURL, { responseType: 'arraybuffer' })
      .then(pickData);
  }
};